/*
 * Copyright 2009 - sanatkumar.padhy
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *  	http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package com.googlecode.simplecrud.dao;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.simplecrud.DatabaseException;
import com.googlecode.simplecrud.SimpleCrudException;
import com.googlecode.simplecrud.annotations.Column;
import com.googlecode.simplecrud.annotations.Entity;

/**
 * Used by the DAO for common tasks like creating connection to hit the database
 * and create column/value maps which is used by all the operations to create
 * queries.
 * 
 * @see SimpleDaoImpl
 * @author sanatkumar.padhy
 */
public abstract class DaoSupport
{

	/** Logger */
	protected Log logger = LogFactory.getLog(this.getClass());

	/** The data source should be set before using the DAO */
	private DataSource dataSource;

	/**
	 * This method takes the query as a string and creates a prepared statement
	 * to be executed. The values are used as the indexed parameters.
	 * 
	 * @param query
	 *            - the insert/update/delete query to be executed
	 * @param values
	 *            - indexed parameters for the query
	 */
	protected void updateDatabase(String query, List<Object> values)
	{
		logger.info("Executing Query --> " + query + " with params" + values);

		Connection connection = null;
		PreparedStatement statement = null;
		int index = 1;
		try {
			connection = getDataSource().getConnection();
			statement = connection.prepareStatement(query.toString());
			for (Object object : values) {
				statement.setObject(index, object);
				index++;
			}
			statement.executeUpdate();
		}
		catch (SQLException e) {
			throw new DatabaseException("Error executing query::", e);
		}
		finally {
			closeResources(statement, connection);
		}
	}

	/**
	 * This method takes the query as a string and creates a prepared statement
	 * for execution using the provided list.
	 * 
	 * @param query
	 *            - the select query to be executed
	 * @param values
	 *            - indexed parameters for the query
	 * @return <code>ResultSet</code>
	 */
	protected ResultSet getResult(String query, List<Object> values)
	{
		logger.info("Executing Query --> " + query + " with params" + values);

		Connection connection;
		PreparedStatement statement;
		ResultSet rs = null;
		int index = 1;
		try {
			connection = getDataSource().getConnection();
			statement = connection.prepareStatement(query);
			for (Object object : values) {
				statement.setObject(index, object);
			}
			rs = statement.executeQuery();
		}
		catch (SQLException e) {
			throw new DatabaseException("Error retrieving records ::", e);
		}
		finally {
			//
		}

		return rs;
	}

	/**
	 * Creates a Map of key columns and the values. columns with null values are
	 * not considered.
	 * 
	 * @param object
	 *            - the persistence object or key
	 * @return <code>Map<String, Object></code>
	 */
	protected Map<String, Object> createKeyValueMap(Object object)
	{
		String column = null;
		Annotation fieldAnn = null;
		Map<String, Object> keyValueMap = new HashMap<String, Object>();

		Field[] fields = object.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			fieldAnn = field.getAnnotation(Column.class);
			if (fieldAnn != null) {
				try {
					column = ((Column) fieldAnn).name();
					Object value = field.get(object);
					if ((value != null) && ((Column) fieldAnn).primaryKey()) {
						keyValueMap.put(column, value);
					}
				}
				catch (IllegalArgumentException e) {
					throw new SimpleCrudException("Illegal argument for field:" + field.getName() + " in "
							+ object.getClass().getSimpleName());
				}
				catch (IllegalAccessException e) {
					throw new SimpleCrudException("Illegal Access to field: " + field.getName());
				}
			}
		}

		return keyValueMap;
	}

	/**
	 * Creates a Map of all columns and the values. columns with null values are
	 * igored.
	 * 
	 * @param dataObject
	 *            - the persistence object
	 * @return <code>Map<String, Object></code>
	 */
	protected Map<String, Object> createColumnValueMap(Object dataObject)
	{
		String column = null;
		Annotation fieldAnn = null;
		Map<String, Object> columnMap = new HashMap<String, Object>();
		Field[] fields = dataObject.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			fieldAnn = field.getAnnotation(Column.class);
			if (fieldAnn != null) {
				column = ((Column) fieldAnn).name();
				try {
					Object value = field.get(dataObject);
					if (value != null) {
						columnMap.put(column, value);
					}
				}
				catch (IllegalArgumentException e) {
					throw new SimpleCrudException("Illegal argument for field:" + field.getName() + " in "
							+ dataObject.getClass().getSimpleName());
				}
				catch (IllegalAccessException e) {
					throw new SimpleCrudException("Illegal Access to field: " + field.getName());
				}
			}
		}
		return columnMap;
	}

	/**
	 * Find the table name using reflection from the Entity annotation
	 * 
	 * @param dataObject
	 *            - the entity or key
	 * @return tableName
	 */
	protected String findTableName(Object dataObject)
	{
		Annotation tableAn = dataObject.getClass().getAnnotation(Entity.class);
		if (tableAn == null) { throw new SimpleCrudException("DataObject is missing @Entity annotation"); }
		return ((Entity) tableAn).name();
	}

	/* Close the resources held by DAO */
	private void closeResources(Statement statement, Connection connection)
	{
		try {
			if (statement != null) {
				statement.close();
			}
			if (connection != null) {
				connection.close();
			}
		}
		catch (SQLException e) {
			logger.error(e);
		}
	}

	/**
	 * @return the dataSource
	 */
	public DataSource getDataSource()
	{
		return dataSource;
	}

	/**
	 * @param dataSource
	 *            the dataSource to set
	 */
	public void setDataSource(DataSource dataSource)
	{
		this.dataSource = dataSource;
	}
}
